package com.sojson.project.sys.menu.service.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantDict;
import com.sojson.enums.core.EGeneralStatus;
import com.sojson.enums.core.EMenuIsCache;
import com.sojson.enums.core.EMenuIsFrame;
import com.sojson.enums.core.EMenuType;
import com.sojson.enums.core.EMenuVisible;
import com.sojson.project.sys.dict.entity.vo.DictDataVo;
import com.sojson.project.sys.menu.dao.MenuDao;
import com.sojson.project.sys.menu.entity.bo.MetaBo;
import com.sojson.project.sys.menu.entity.bo.RouterBo;
import com.sojson.project.sys.menu.entity.bo.TreeSelect;
import com.sojson.project.sys.menu.entity.dto.MenuDto;
import com.sojson.project.sys.menu.entity.dto.MenuPageDto;
import com.sojson.project.sys.menu.entity.po.Menu;
import com.sojson.project.sys.menu.entity.vo.MenuVo;
import com.sojson.project.sys.menu.service.MenuService;
import com.sojson.project.sys.role.dao.RoleDao;
import com.sojson.project.sys.role.dao.RoleMenuDao;
import com.sojson.project.sys.role.entity.po.Role;
import com.sojson.project.sys.role.service.RoleService;
import com.sojson.util.DictUtil;
import com.sojson.util.ExpUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.param.ParamUtil;

/**
 * 菜单权限表Service实现类
 * 
 * @author liu
 * @date 2022-09-08
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuDao, Menu> implements MenuService {

    @Lazy
    @Autowired
    private RoleService roleService;
    @Resource
    private MenuDao menuDao;
    @Resource
    private RoleDao roleDao;
    @Resource
    private RoleMenuDao roleMenuDao;

    /**
     * 单条插入
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean add(Menu dto) throws IOException {
        menuDao.insert(dto);
        return true;
    }

    /**
     * 通过id更新
     * 
     * @param dto
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean update(Menu dto) throws IOException {
        Menu selectById = menuDao.selectById(dto.getId());
        if (selectById == null) {
            return true;
        }

        String parentIdNew = dto.getParentId();
        String parentIdOld = selectById.getParentId();
        // 新权限不是空并且跟旧权限不一致就校验旧权限
        if (StringUtil.isNotBlank(parentIdNew) && !parentIdNew.equals(parentIdOld)) {
            ParamUtil.verifyMenuIdExist(parentIdOld);
        }

        // 修改菜单
        menuDao.updateById(dto);
        return true;
    }

    /**
     * 列表查询
     * 
     * @param dto
     * @return
     */
    @Override
    public List<MenuVo> list(MenuDto dto) {
        List<MenuVo> list = menuDao.list(dto);
        return list;
    }

    /**
     * 分页查询
     * 
     * @param dto
     * @return
     */
    @Override
    public List<MenuVo> page(MenuPageDto dto) {
        List<MenuVo> page = menuDao.page(dto);
        return page;
    }

    /**
     * 通过id查询
     * 
     * @param id
     * @return
     */
    @Override
    public MenuVo findById(String id) {
        return menuDao.findById(id);
    }

    /**
     * 通过对象删除
     * 
     * @param dto
     * @return
     */
    @Override
    public boolean removeByDto(MenuDto dto) {
        menuDao.removeByDto(dto);
        return true;
    }

    /**
     * 通过id删除
     * 
     * @param id
     * @return
     * @throws IOException 
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeById(String id) throws IOException {
        int hasChildByMenuId = menuDao.hasChildByMenuId(id);
        if (hasChildByMenuId > 0) {
            ExpUtil.throwEx("存在子菜单,不允许删除");
        }
        int checkMenuExistRole = roleMenuDao.checkMenuExistRole(id);
        if (checkMenuExistRole > 0) {
            ExpUtil.throwEx("菜单已分配,不允许删除");
        }

        // 删除当前菜单
        menuDao.deleteById(id);

        // 删除角色关联的菜单
        roleMenuDao.deleteByMenuId(id);
        return true;
    }

    /**
     * 清空数据
     * 
     * @return
     */
    @Override
    public boolean clear() {
        menuDao.clear();
        return true;
    }

    /**
     * 通过id修改状态
     * 
     * @param dto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStatus(Menu dto) throws IOException {
        menuDao.updateById(dto);
        return true;
    }

    /**
     * 根据用户ID查询菜单树信息
     *
     * @param dto
     * @return 菜单列表
     */
    @Override
    public List<MenuVo> getMenuTreeByUserId(MenuDto dto) {
        List<MenuVo> menus = null;
        if (ParamUtil.isAdminSuper(roleService.getRolesByUserId(dto.getUserId()))) {
            dto.setUserId(null);
            menus = menuDao.getMenuTreeAll(dto);
        } else {
            menus = menuDao.getMenuTreeByUserId(dto);
        }

        return getChildPerms(menus, Constant.MENU_PARENT_ID);
    }

    /**
     * 根据父节点的ID获取所有子节点
     *
     * @param list     分类表
     * @param parentId 传入的父节点ID
     * @return String
     */
    public List<MenuVo> getChildPerms(List<MenuVo> list, String parentId) {
        List<MenuVo> returnList = new ArrayList<>();
        for (Iterator<MenuVo> iterator = list.iterator(); iterator.hasNext();) {
            MenuVo t = iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId().equals(parentId)) {
                recursionFn(list, t);
                returnList.add(t);
            }
        }
        return returnList;
    }

    /**
     * 递归列表
     *
     * @param list
     * @param t
     */
    private void recursionFn(List<MenuVo> list, MenuVo t) {
        // 得到子节点列表
        List<MenuVo> childList = getChildList(list, t);
        t.setChildren(childList);
        for (MenuVo tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<MenuVo> getChildList(List<MenuVo> list, MenuVo t) {
        List<MenuVo> tlist = new ArrayList<>();
        Iterator<MenuVo> it = list.iterator();
        while (it.hasNext()) {
            MenuVo n = it.next();
            if (n.getParentId().equals(t.getId()) && t.getStatus() == EGeneralStatus.NORMAL.getCode()) {
                tlist.add(n);
            }
        }
        return tlist;
    }

    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<MenuVo> list, MenuVo t) {
        return getChildList(list, t).size() > 0;
    }

    /**
     * 构建前端路由所需要的菜单
     *
     * @param menus 菜单列表
     * @return 路由列表
     */
    @Override
    public List<RouterBo> buildMenus(List<MenuVo> menus) {
        List<RouterBo> routers = new LinkedList<>();
        for (MenuVo menu : menus) {
            RouterBo router = new RouterBo();
            router.setParentId(menu.getParentId());
            router.setHidden(menu.getVisible() == EMenuVisible.NO.getCode());
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setMeta(new MetaBo(menu.getName(), menu.getIcon(), menu.getIsCache() == EMenuIsCache.NO.getCode()));
            List<MenuVo> cMenus = menu.getChildren();
            if (!cMenus.isEmpty() && cMenus.size() > 0 && EMenuType.M.getCode() == menu.getType()) {
                router.setAlwaysShow(true);
                router.setRedirect("noRedirect");
                router.setChildren(buildMenus(cMenus));
            } else if (isMeunFrame(menu)) {
                List<RouterBo> childrenList = new ArrayList<>();
                RouterBo children = new RouterBo();
                children.setPath(menu.getPath());
                children.setComponent(menu.getComponent());
                children.setName(StringUtil.capitalize(menu.getName()));
                children.setMeta(new MetaBo(menu.getName(), menu.getIcon(), menu.getIsCache() == 2));
                childrenList.add(children);
                router.setChildren(childrenList);
            }
            routers.add(router);
        }
        return routers;
    }

    /**
     * 获取路由名称
     *
     * @param menu 菜单信息
     * @return 路由名称
     */
    public String getRouteName(MenuVo menu) {
        String routerName = StringUtil.capitalize(menu.getName());
        // 非外链并且是一级目录（类型为目录）
        if (isMeunFrame(menu)) {
            routerName = StringUtil.EMPTY;
        }
        return routerName;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMeunFrame(MenuVo menu) {
        return menu.getParentId().equals("0") && EMenuType.C.getCode() == menu.getType()
            && menu.getIsFrame() == EMenuIsFrame.NO.getCode();
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(MenuVo menu) {
        String routerPath = menu.getPath();
        // 非外链并且是一级目录（类型为目录）
        if ("0".equals(menu.getParentId()) && EMenuType.M.getCode() == menu.getType()
            && EMenuIsFrame.NO.getCode() == menu.getIsFrame()) {
            routerPath = "/" + menu.getPath();
        }
        // 非外链并且是一级目录（类型为菜单）
        else if (isMeunFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(MenuVo menu) {
        String component = Constant.LAYOUT;
        if (StringUtil.isNotBlank(menu.getComponent()) && !isMeunFrame(menu)) {
            component = menu.getComponent();
        }
        return component;
    }

    /**
     * 根据用户查询系统菜单列表
     *
     * @param dto  菜单信息
     * @return 菜单列表
     */
    @Override
    public List<MenuVo> selectMenuList(MenuDto dto) {
        List<MenuVo> menuList = null;
        // 管理员显示所有菜单信息
        if (ParamUtil.isAdminSuper(roleService.getRolesByUserId(dto.getUserId()))) {
            dto.setUserId(null);
            menuList = menuDao.selectMenuList(dto);
        } else {
            menuList = menuDao.selectMenuListByUserId(dto);
        }
        return menuList;
    }

    /**
     * 构建前端所需要下拉树结构
     *
     * @param menus 菜单列表
     * @return 下拉树结构列表
     * @throws IOException 
     */
    @Override
    public List<TreeSelect> buildMenuTreeSelect(List<MenuVo> menus) throws IOException {
        // 最终菜单列表集合
        List<MenuVo> ms = new ArrayList<>(5);
        // 最终菜单列表map集合
        Map<String, List<MenuVo>> map = new LinkedHashMap<>(5);
        // 环境名称集合
        Map<String, String> environmentNames = new LinkedHashMap<>(5);

        // 设置环境名称
        List<DictDataVo> gets = DictUtil.gets(ConstantDict.ENV_USER_TYPE);
        for (DictDataVo dictData : gets) {
            environmentNames.put(dictData.getVal(), dictData.getName());
        }

        // 菜单对环境进行分类
        for (MenuVo menuVo : menus) {
            String environment = menuVo.getEnvironment();

            List<MenuVo> list = map.get(environment);
            if (list == null) {
                list = new ArrayList<>(20);
                map.put(environment, list);
            }
            list.add(menuVo);
        }

        // 将每个环境以树的方式排列
        Set<Entry<String, List<MenuVo>>> entrySet = map.entrySet();
        for (Entry<String, List<MenuVo>> entry : entrySet) {
            String key = entry.getKey();
            List<MenuVo> value = entry.getValue();
            String environmentName = environmentNames.get(key);
            // 环境不存在就过滤
            if (StringUtil.isBlankString(environmentName)) {
                continue;
            }

            List<MenuVo> menuTrees = buildMenuTree(value);
            MenuVo menuVo = new MenuVo();
            menuVo.setId(key);
            menuVo.setName(environmentName);
            menuVo.setChildren(menuTrees);
            ms.add(menuVo);
        }
        return ms.stream().map(TreeSelect::new).collect(Collectors.toList());
    }

    /**
     * 构建前端所需要树结构
     *
     * @param menus 菜单列表
     * @return 树结构列表
     * @throws IOException 
     */
    @Override
    public List<MenuVo> buildMenuTree(List<MenuVo> menus) throws IOException {
        List<MenuVo> returnList = new ArrayList<>();
        for (Iterator<MenuVo> iterator = menus.iterator(); iterator.hasNext();) {
            MenuVo menu = iterator.next();
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (Constant.MENU_PARENT_ID.equals(menu.getParentId())) {
                recursionFn(menus, menu);
                returnList.add(menu);
            }
        }
        if (returnList.isEmpty()) {
            returnList = menus;
        }
        return returnList;
    }

    /**
     * 根据用户查询系统菜单列表
     *
     * @param userId 用户ID
     * @return 菜单列表
     */
    @Override
    public List<MenuVo> selectMenuList(String userId) {
        MenuDto dto = new MenuDto();
        dto.setUserId(userId);
        return selectMenuList(dto);
    }

    /**
     * 根据角色ID查询菜单树信息
     *
     * @param roleId 角色ID
     * @return 选中菜单列表
     */
    @Override
    public List<String> selectMenuListByRoleId(String roleId) {
        Role role = roleDao.selectById(roleId);
        return menuDao.selectMenuListByRoleId(roleId, role.getMenuCheckStrictly());
    }

    /**
     * 获取超级管理员角色数量
     * 
     * @param userIds
     * @return
     */
    @Override
    public int getAdminSuperRoleNumByUserIds(List<String> userIds) {
        return menuDao.getAdminSuperRoleNumByUserIds(userIds);
    }

    /**
     * 获取超级管理员角色数量
     * 
     * @param roleIds
     * @return
     */
    @Override
    public int getAdminSuperRoleNumByRoleIds(Set<String> roleIds) {
        return menuDao.getAdminSuperRoleNumByRoleIds(roleIds);
    }

}